* Subclass this object to add a new result type
* to the search system, then call registerProvider()
* in SearchSystem with an instance.
+ * By default, search is synchronous and uses the
+ * getInitialResultSet()/getSubsearchResultSet() methods.
+ * For asynchronous search, set the async property to true
+ * and implement getInitialResultSetAsync()/getSubsearchResultSetAsync()
+ * instead.
*/
const SearchProvider = new Lang.Class({
Name: 'SearchProvider',
_init: function(title) {
this.title = title;
this.searchSystem = null;
- this.searchAsync = false;
- },
-
- _asyncCancelled: function() {
- },
-
- startAsync: function() {
- this.searchAsync = true;
- },
-
- tryCancelAsync: function() {
- if (!this.searchAsync)
- return;
- this._asyncCancelled();
- this.searchAsync = false;
- },
-
- /**
- * addItems:
- * @items: an array of result identifier strings representing
- * items which match the last given search terms.
- *
- * This should be used for something that requires a bit more
- * logic; it's designed to be an asyncronous way to add a result
- * to the current search.
- */
- addItems: function(items) {
- if (!this.searchSystem)
- throw new Error('Search provider not registered');
-
- if (!items.length)
- return;
-
- this.tryCancelAsync();
-
- this.searchSystem.addProviderItems(this, items);
+ this.async = false;
},
/**
throw new Error('Not implemented');
},
+ /**
+ * getInitialResultSetAsync:
+ * @terms: Array of search terms, treated as logical AND
+ *
+ * Like getInitialResultSet(), but the method should return immediately
+ * without a return value - use SearchSystem.pushResults() when the
+ * corresponding results are ready.
+ */
+ getInitialResultSetAsync: function(terms) {
+ throw new Error('Not implemented');
+ },
+
/**
* getSubsearchResultSet:
* @previousResults: Array of item identifiers
throw new Error('Not implemented');
},
+ /**
+ * getSubsearchResultSetAsync:
+ * @previousResults: Array of item identifiers
+ * @newTerms: Updated search terms
+ *
+ * Like getSubsearchResultSet(), but the method should return immediately
+ * without a return value - use SearchSystem.pushResults() when the
+ * corresponding results are ready.
+ */
+ getSubsearchResultSetAsync: function(previousResults, newTerms) {
+ throw new Error('Not implemented');
+ },
+
/**
* getResultMetas:
* @ids: Result identifier strings
throw new Error('Not implemented');
},
+ /**
+ * getResultMetasAsync:
+ * @ids: Result identifier strings
+ * @callback: callback to pass the results to when ready
+ *
+ * Like getResultMetas(), but the method should return immediately
+ * without a return value - pass the results to the provided @callback
+ * when ready.
+ */
+ getResultMetasAsync: function(ids, callback) {
+ throw new Error('Not implemented');
+ },
+
/**
* createResultContainer:
*
this._previousResults = [];
},
- addProviderItems: function(provider, items) {
- this.emit('search-updated', provider, items);
+ pushResults: function(provider, results) {
+ let i = this._providers.indexOf(provider);
+ if (i == -1)
+ return;
+
+ this._previousResults[i] = [provider, results];
+ this.emit('search-updated', this._previousResults[i]);
},
updateSearch: function(searchString) {
if (isSubSearch) {
for (let i = 0; i < this._providers.length; i++) {
let [provider, previousResults] = this._previousResults[i];
- provider.tryCancelAsync();
try {
- let providerResults = provider.getSubsearchResultSet(previousResults, terms);
- results.push([provider, providerResults]);
+ if (provider.async) {
+ provider.getSubsearchResultSetAsync(previousResults, terms);
+ results.push([provider, []]);
+ } else {
+ let providerResults = provider.getSubsearchResultSet(previousResults, terms);
+ results.push([provider, providerResults]);
+ }
} catch (error) {
global.log ('A ' + error.name + ' has occured in ' + provider.title + ': ' + error.message);
}
} else {
for (let i = 0; i < this._providers.length; i++) {
let provider = this._providers[i];
- provider.tryCancelAsync();
try {
- let providerResults = provider.getInitialResultSet(terms);
- results.push([provider, providerResults]);
+ if (provider.async) {
+ provider.getInitialResultSetAsync(terms);
+ results.push([provider, []]);
+ } else {
+ let providerResults = provider.getInitialResultSet(terms);
+ results.push([provider, providerResults]);
+ }
} catch (error) {
global.log ('A ' + error.name + ' has occured in ' + provider.title + ': ' + error.message);
}
if (results.length == 0)
return;
- let metas = provider.getResultMetas(results);
- this.renderResults(metas);
+ if (provider.async) {
+ provider.getResultMetasAsync(results,
+ Lang.bind(this, this.renderResults));
+ } else {
+ let metas = provider.getResultMetas(results);
+ this.renderResults(metas);
+ }
}));
}));
this._notDisplayedResult = [];
this._terms = [];
+ this._pendingClear = false;
},
getResultsForDisplay: function() {
+ let alreadyVisible = this._pendingClear ? 0 : this._grid.visibleItemsCount();
let canDisplay = this._grid.childrenInRow(this._width) * MAX_SEARCH_RESULTS_ROWS
- - this._grid.visibleItemsCount();
+ - alreadyVisible;
let numResults = Math.min(this._notDisplayedResult.length, canDisplay);
// copy the lists
this._notDisplayedResult = results.slice(0);
this._terms = terms.slice(0);
+ this._pendingClear = true;
},
renderResults: function(metas) {
},
clear: function () {
- this._terms = [];
- this._notDisplayedResult = [];
this._grid.removeAll();
this.selectionIndex = -1;
+ this._pendingClear = false;
},
selectIndex: function (index) {
this._providerMeta.push({ provider: provider,
actor: providerBox,
- resultDisplay: resultDisplay });
+ resultDisplay: resultDisplay,
+ hasPendingResults: false });
this._content.add(providerBox);
},
}
},
- _clearDisplayForProvider: function(index) {
- let meta = this._providerMeta[index];
+ _clearDisplayForProvider: function(provider) {
+ let meta = this._metaForProvider(provider);
meta.resultDisplay.clear();
meta.actor.hide();
},
return this._providerMeta[this._providers.indexOf(provider)];
},
- _updateCurrentResults: function(searchSystem, provider, results) {
+ _maybeSetInitialSelection: function() {
+ if (this._selectedOpenSearchButton > -1 || this._selectedProvider > -1)
+ return;
+
+ for (let i = 0; i < this._providerMeta.length; i++) {
+ let meta = this._providerMeta[i];
+ if (meta.hasPendingResults)
+ return;
+
+ if (meta.actor.visible)
+ break; // select this one!
+ }
+
+ this.selectDown(false);
+ this._initialSelectionSet = true;
+ },
+
+ _updateCurrentResults: function(searchSystem, results) {
let terms = searchSystem.getTerms();
+ let [provider, providerResults] = results;
let meta = this._metaForProvider(provider);
- meta.resultDisplay.clear();
- meta.actor.show();
- meta.resultDisplay.setResults(providerResults, terms);
- let displayResults = meta.resultDisplay.getResultsForDisplay();
- meta.resultDisplay.renderResults(provider.getResultMetas(displayResults));
- return true;
+ meta.hasPendingResults = false;
+ this._updateProviderResults(provider, providerResults, terms);
+ },
+
+ _updateProviderResults: function(provider, providerResults, terms) {
+ let meta = this._metaForProvider(provider);
+ if (providerResults.length == 0) {
+ this._clearDisplayForProvider(provider);
+ meta.resultDisplay.setResults([], []);
+ } else {
+ this._providerMetaResults[provider.title] = providerResults;
+ meta.resultDisplay.setResults(providerResults, terms);
+ let results = meta.resultDisplay.getResultsForDisplay();
+
+ if (provider.async) {
+ provider.getResultMetasAsync(results, Lang.bind(this,
+ function(metas) {
+ this._clearDisplayForProvider(provider);
+ meta.actor.show();
+ this._content.hide();
+ meta.resultDisplay.renderResults(metas);
+ this._maybeSetInitialSelection();
+ this._content.show();
+ }));
+ } else {
+ let metas = provider.getResultMetas(results);
+ this._clearDisplayForProvider(provider);
+ meta.actor.show();
+ meta.resultDisplay.renderResults(metas);
+ }
+ }
},
_updateResults: function(searchSystem, results) {
} else {
this._selectedOpenSearchButton = -1;
this._updateOpenSearchButtonState();
+ this._selectedProvider = -1;
this._statusText.hide();
}
for (let i = 0; i < results.length; i++) {
let [provider, providerResults] = results[i];
- if (providerResults.length == 0) {
- this._clearDisplayForProvider(i);
- } else {
- this._providerMetaResults[provider.title] = providerResults;
- this._clearDisplayForProvider(i);
- let meta = this._metaForProvider(provider);
- meta.actor.show();
- meta.resultDisplay.setResults(providerResults, terms);
- let displayResults = meta.resultDisplay.getResultsForDisplay();
- meta.resultDisplay.renderResults(provider.getResultMetas(displayResults));
- }
+ let meta = this._metaForProvider(provider);
+ meta.hasPendingResults = provider.async;
+ if (!meta.hasPendingResults)
+ this._updateProviderResults(provider, providerResults, terms);
}
- if (this._selectedOpenSearchButton == -1)
- this.selectDown(false);
-
+ this._maybeSetInitialSelection();
this._content.show();
return true;